package com.atguigu.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.SkuInfoTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.seckill.feign.CouponFeignService;
import com.atguigu.gulimall.seckill.feign.ProductFeignService;
import com.atguigu.gulimall.seckill.service.SeckillService;
import com.atguigu.gulimall.seckill.to.SeckillSkuRedisTo;
import com.atguigu.gulimall.seckill.vo.SeckillSessionsWithSkusVo;
import com.atguigu.gulimall.seckill.vo.SeckillSkuVo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redissonClient;

    private final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+商品随机码

    @Override
    public void uploadSeckillSkuLatest3Days() {
        // 去数据库扫描需要参与秒杀的活动
        R r = couponFeignService.getLatest3DaysSession();
        if (r.getCode() == 0) {
            List<SeckillSessionsWithSkusVo> sessionsWithSkusVos = r.getData(new TypeReference<List<SeckillSessionsWithSkusVo>>() {
            });
            // 保存活动信息
            saveSessionInfo(sessionsWithSkusVos);
            // 保存活动相关联的商品信息
            saveSessionSkuInfo(sessionsWithSkusVos);
        }
    }

    /**
     * 获取当前时间可以参与的秒杀商品
     *
     * @return
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        // 1.确定当前时间，属于哪个场次
        // 1970-至今
        long how = new Date().getTime();
        // 得到所有场次
        Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*");
        for (String key : keys) {
            String[] split = key
                    .replace(SESSION_CACHE_PREFIX, "")
                    .split("_");
            long startTime = Long.parseLong(split[0]);
            long endTime = Long.parseLong(split[1]);
            if (how >= startTime && how <= endTime) {
                // 当前要用的场次信息，查询场次的商品信息
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> skuList = hashOps.multiGet(range);
                if (!CollectionUtils.isEmpty(skuList)) {
                    return skuList.stream().map(item -> {
                        SeckillSkuRedisTo skuRedisTo = JSON.parseObject(item, SeckillSkuRedisTo.class);
                        // todo 如果秒杀没开始，要将随机码 脱敏
                        // skuRedisTo.setRandomCode(null);
                        return skuRedisTo;
                    }).collect(Collectors.toList());
                }
                break;
            }
        }
        return null;
    }

    private void saveSessionInfo(List<SeckillSessionsWithSkusVo> sessionsWithSkusVos) {
        for (SeckillSessionsWithSkusVo sessionsWithSkusVo : sessionsWithSkusVos) {
            if (!CollectionUtils.isEmpty(sessionsWithSkusVo.getRelationSkus())) {
                long startTime = sessionsWithSkusVo.getStartTime().getTime();
                long endTime = sessionsWithSkusVo.getEndTime().getTime();
                String key = SESSION_CACHE_PREFIX + startTime + "_" + endTime;
                // todo 如果key已存在，就不用保存了
                if (!redisTemplate.hasKey(key)) {
                    List<String> skuIdList = sessionsWithSkusVo.getRelationSkus()
                            .stream()
                            .map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString())
                            .collect(Collectors.toList());
                    redisTemplate.opsForList()
                            .leftPushAll(key, skuIdList);
                }
            }
        }
    }

    private void saveSessionSkuInfo(List<SeckillSessionsWithSkusVo> sessionsWithSkusVos) {
        // 准备hash操作
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        for (SeckillSessionsWithSkusVo sessionsWithSkusVo : sessionsWithSkusVos) {
            sessionsWithSkusVo.getRelationSkus().forEach(seckillSkuVo -> {
                String currentKey = seckillSkuVo.getPromotionSessionId() + "_" + seckillSkuVo.getSkuId();
                if (!hashOps.hasKey(currentKey)) {
                    SeckillSkuRedisTo seckillSkuRedisTo = new SeckillSkuRedisTo();
                    // 秒杀数据
                    BeanUtils.copyProperties(seckillSkuVo, seckillSkuRedisTo);
                    // 查询sku基本信息
                    R r = productFeignService.info(seckillSkuVo.getSkuId());
                    if (r.getCode() == 0) {
                        SkuInfoTo skuInfoTo = r.getData("skuInfo", new TypeReference<SkuInfoTo>() {
                        });
                        seckillSkuRedisTo.setSkuInfo(skuInfoTo);
                    }
                    // 设置当前秒杀商品的时间范围
                    seckillSkuRedisTo.setStartTime(sessionsWithSkusVo.getStartTime().getTime());
                    seckillSkuRedisTo.setEndTime(sessionsWithSkusVo.getEndTime().getTime());
                    /**
                     * todo 保存一个随机码
                     * 想要来秒杀，不能光传skuid，这样只有秒杀开始之后，才知道随机码和skuid的对应关系
                     * 避免恶意秒杀
                     */
                    String randomCode = UUID.randomUUID().toString().replace("-", "");
                    seckillSkuRedisTo.setRandomCode(randomCode);
                    hashOps.put(currentKey, JSON.toJSONString(seckillSkuRedisTo));

                    // 如果当前场次的信息已经上架，就不需要上架了
                    /**
                     * 设置商品的分布式信号量，作为库存扣减信息
                     * 一大作用：限流
                     */
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                    semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());
                }
            });
        }
    }
}
